Skip to content

fix(csharp): self-healing CI release pipeline for NuGet + GitHub#85

Merged
konard merged 3 commits into
mainfrom
issue-84-0f4c3bcac49c
May 12, 2026
Merged

fix(csharp): self-healing CI release pipeline for NuGet + GitHub#85
konard merged 3 commits into
mainfrom
issue-84-0f4c3bcac49c

Conversation

@konard
Copy link
Copy Markdown
Member

@konard konard commented May 12, 2026

Fixes #84

Summary

The C# release workflow was version-bumped, tagged, and pushed for csharp-v2.4.0, but dotnet nuget push returned HTTP 403 mid-flight, so clink on NuGet stayed at 2.3.0 and no GitHub release was created. The next push to main short-circuited on the pre-existing tag + version commit and never retried.

This PR ports the self-healing release pattern from js-ai-driven-development-pipeline-template to C# and rewires csharp.yml to resume mid-failed releases on the next push to main, without requiring a new changeset.

Root cause

  • Every publish/verify/release step gated only on steps.version.outputs.version_committed == 'true'.
  • version-and-commit.mjs returns already_released=true when the bumped tag already exists, so version_committed is 'false' on every retry after the first run pushed the tag.
  • There was no idempotent probe of the NuGet flat-container index or the GitHub Releases endpoint.
  • NUGET_API_KEY was never validated upfront; the expired key only surfaced mid-dotnet nuget push.

Full timeline, evidence captures, and template comparison are in docs/case-studies/issue-84/README.md (added in commit 7f2be75).

Changes

csharp/scripts/check-release-needed.mjs (new)

Pure decide() function plus async probes:

  • https://api.nuget.org/v3-flatcontainer/{id-lower}/index.json for the csproj <Version> (returns null on 404).
  • GET /repos/{owner}/{repo}/releases/tags/csharp-v{version} for the matching GitHub release.

Outputs to GITHUB_OUTPUT: should_release, skip_bump, current_version, nuget_published, github_release_exists, reason. Supports NUGET_INDEX_URL / GITHUB_API_URL overrides for tests.

.github/workflows/csharp.yml

  • Inserted "Check if release is needed" step between changeset detection and version-and-commit.
  • Inserted "Validate NuGet API key" upfront, with Resolve release version so downstream steps see the right <Version> even on self-healing reruns.
  • Rewrote 5 release-job and 4 instant-release if: conditions to the union:
    if: >-
      steps.version.outputs.version_committed == 'true' ||
      steps.version.outputs.already_released == 'true' ||
      (steps.check_release.outputs.should_release == 'true' &&
       steps.check_release.outputs.skip_bump == 'true')
  • Verify package on NuGet and Create GitHub Release now read steps.release_version.outputs.version so self-healing reruns publish the correct artifact.

Tests

  • csharp/scripts/release-scripts.test.mjs: 8 new tests covering the pure decide() function, csproj parsing, and the CLI end-to-end via a 127.0.0.1 mock HTTP server for NuGet + GitHub probes. 12 tests total, all pass (~700ms).
  • js/test/repositoryLayout.test.mjs: new regression guard asserting the self-healing gates remain in csharp.yml — the "Check if release is needed" step, check-release-needed.mjs invocation, at least 4 should_release && skip_bump gates, and at least 5 already_released gates. 21 tests total, all pass.

Cleanup

  • Removed the stray root .gitkeep that violated the existing repositoryLayout test.

Upstream report

The same defect class is present in csharp-ai-driven-development-pipeline-template — filed issue #11 with the same diagnosis and a pointer back to this PR.

Test plan

  • node --test js/test/*.test.mjs csharp/scripts/*.test.mjs → 21/21 pass locally.
  • python3 -c "import yaml; yaml.safe_load(open('.github/workflows/csharp.yml'))" → valid YAML.
  • Reviewed gh pr diff 85 for unintended deletions — only the narrow if: conditions and the broadened execFile import.
  • CI green on the PR.
  • After merge: next push to main triggers the workflow; check-release-needed correctly reports clink@2.4.0 already on NuGet (once the manual republish or self-heal lands) and short-circuits — or, if 2.4.0 is still missing, it resumes the publish + GitHub release without a new changeset.

References

Adding .gitkeep for PR creation (default mode).
This file will be removed when the task is complete.

Issue: #84
@konard konard self-assigned this May 12, 2026
konard added 2 commits May 12, 2026 20:12
Compile run logs, NuGet/GitHub release state, and upstream template
sources used to design the fix. Captures the HTTP 403 NuGet publish
failure on attempt 1 of run 25757419575 and the silent no-op on
attempt 2 that left csharp-v2.4.0 stuck (tag pushed, package missing,
release missing).
Issue #84: a release was version-bumped, tagged, and pushed, but the
NuGet publish step returned HTTP 403, leaving package clink stuck at
2.3.0 even though tag csharp-v2.4.0 and commit b52c8f1 were already on
main. Re-runs short-circuited on the pre-existing tag/commit and never
retried the publish.

Port the self-healing pattern from
js-ai-driven-development-pipeline-template's check-release-needed.mjs
to C#: a new script probes the NuGet flat-container index plus the
GitHub Releases endpoint for the csproj <Version>, and emits
should_release / skip_bump signals. The release and instant-release
jobs now gate every publish + GitHub-release step on the union of
version_committed, already_released, and (should_release && skip_bump)
so that a stalled deployment resumes on the next push to main without
requiring a new changeset.

An upfront "Validate NuGet API key" step also surfaces missing/expired
secrets before any publish attempt.

Tests:
- csharp/scripts/release-scripts.test.mjs: 8 new tests covering the
  pure decide() function, csproj parsing, and the CLI end-to-end via a
  127.0.0.1 mock for NuGet + GitHub Releases.
- js/test/repositoryLayout.test.mjs: regression guard asserting the
  self-healing gates remain in csharp.yml (Check if release is needed
  step + 4+ should_release gates + 5+ already_released gates).
- Remove the stray root .gitkeep that violated the existing
  repositoryLayout test.

Refs: docs/case-studies/issue-84/README.md
@konard konard changed the title [WIP] CI/CD: C# failed to deliver NuGet and GitHub releases fix(csharp): self-healing CI release pipeline for NuGet + GitHub May 12, 2026
@konard konard marked this pull request as ready for review May 12, 2026 20:35
@konard
Copy link
Copy Markdown
Member Author

konard commented May 12, 2026

Working session summary

All CI checks pass. Final summary:

Done — PR #85 ready for review

What the fix does: ports check-release-needed.mjs from the JS template, gates every C# release-job publish/verify/release step on version_committed || already_released || (should_release && skip_bump), adds upfront NUGET_API_KEY validation, and a regression test in js/test/repositoryLayout.test.mjs that pins the self-healing gates in csharp.yml. Removed a stray root .gitkeep that violated the existing layout test.


This summary was automatically extracted from the AI working session output.

@konard
Copy link
Copy Markdown
Member Author

konard commented May 12, 2026

🤖 Solution Draft Log

This log file contains the complete execution trace of the AI solution draft process.

💰 Cost: $13.296221

📊 Context and tokens usage:

Claude Opus 4.7: (4 sub-sessions)

  1. 117.7K / 1M (12%) input tokens, 14.6K / 128K (11%) output tokens
  2. 117.9K / 1M (12%) input tokens, 26.3K / 128K (21%) output tokens
  3. 117.1K / 1M (12%) input tokens, 34.8K / 128K (27%) output tokens
  4. 72.5K / 1M (7%) input tokens, 12.3K / 128K (10%) output tokens

Total: (6.1K new + 381.4K cache writes + 16.3M cache reads) input tokens, 109.1K output tokens, $13.296221 cost

🤖 Models used:

  • Tool: Anthropic Claude Code
  • Requested: opus
  • Model: Claude Opus 4.7 (claude-opus-4-7)

📎 Log file uploaded as Gist (5410KB)


Now working session is ended, feel free to review and add any feedback on the solution draft.

@konard konard merged commit 0c8092c into main May 12, 2026
15 checks passed
@konard
Copy link
Copy Markdown
Member Author

konard commented May 12, 2026

🎉 Auto-merged

This pull request has been automatically merged by hive-mind.

  • All CI checks have passed

Auto-merged by hive-mind with --auto-merge flag

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CI/CD: C# failed to deliver NuGet and GitHub releases

1 participant